require 'open-uri'
require 'fileutils'

# one source of reports (e.g. Joe's blog, Fatima's Facebook group)
class Source < ActiveRecord::Base
  has_many(:reports)
  
  validates(:name, :presence => true, :uniqueness => true, :format => {:with => /^[a-z0-9 ]+$/i})
  validates(:kind, :presence => true)
  validate(:only_one_twitter)
  
  before_validation(:trim_keywords)
  before_save(:mark_for_reset)
  before_destroy(:check_assoc)
  after_destroy(:raise_flag)
  after_create(:raise_flag)
  before_create(:set_update_freq)
  
  attr_accessor(:crawler)
  attr_writer(:deadline)
  
  @@flag_file_path = File.join(Rails.root, "tmp/source_flag_file")
  
  # loads reports sorted by type. params are same as those passed to 
  # find/paginate. including :paginate => true results in pagination
  def self.sorted(params = {})
    func = params.delete(:paginate) ? "paginate" : "find"
    send(func, :all, params.merge(:order => "type"))
  end
  
  # total number of reports in the queue
  def self.total_queue
    `find #{queue_parent_dir} -type f | wc -l`.strip.to_i
  end
  
  # gets the parent directory for the source queues
  def self.queue_parent_dir
    File.join(Rails.root, "tmp", "source_queues")
  end
  
  # returns all enabled sources
  def self.enabled
    find_all_by_enabled(true)
  end
  
  # returns all source types (each should have a corresponding subclass of the Source class)
  def self.types
    %w(Rss Twitter FacebookGroup)
  end
  
  def self.create_flag_file
    File.open(@@flag_file_path, "w") {}
  end
  
  def self.check_for_flag_file?
    File.exists?(@@flag_file_path)
  end
  
  def self.remove_flag_file
    File.unlink(@@flag_file_path) rescue nil
  end

  # the source type without the Source suffix
  def kind
    (type || "").gsub(/Source$/, "")
  end
  
  # need to be able to set this for when the class is being created
  def kind=(k)
    self[:type] = k + "Source"
  end

  # shortcuts for adding different types of source events (see private add_event method)
  def add_info(msg) add_event("info", msg) end
  def add_warning(msg) add_event("warning", msg) end
  def add_error(msg) add_event("error", msg) end
  
  # returns keywords split into an array
  def keywords_list
    (keywords || "").split(",")
  end
  
  # returns languages split into an array
  def lang_list
    (languages || "").split(",")
  end
  
  # checks if this source accpepts the given lang
  # (langs are two letter standard ISO codes)
  def accepts_lang?(lang)
    lang_list.empty? || lang_list.include?(lang)
  end
  
  # reloads fields from DB and clears the reset_needed field
  def reset!
    reload
    
    self.reset_needed = false
    self.save(:validate => false)

    # update deadline to now to ensure immediate processing
    @deadline = Time.now
  end
  
  def fetch
    
  end
  
  def deadline
    @deadline ||= Time.now
  end
  
  def update_deadline
    @deadline = Time.now + update_freq.seconds
    #add_info("Updated deadline to #{@deadline}")
  end

  def create_report(attribs)
    begin
      reports.create!(attribs)
    rescue ActiveRecord::RecordInvalid
      add_error("Error creating report: #{r.errors.full_messages.join(',')}")
    end
  end
  
  # writes contents to filename in the appropriate directory for this source
  def enqueue(filename, contents)
    ensure_queue_dir
    File.open(File.join(queue_dir, filename), "w"){|f| f.write(contents)}
  end
  
  # loads and deletes file from the queue_dir
  # returns nil if none exists
  def dequeue
    ensure_queue_dir
    dir = queue_dir
    Dir.entries(dir).each do |f|
      # skip hidden files
      next if f[0,1] == "."
      # get the full path
      path = File.join(dir, f)
      # get the contents
      contents = File.read(path)
      # delete the file
      File.delete(path)
      # return the contents
      return contents
    end
    return nil
  end
  
  # counts number of files in queue
  def queue_size
    Dir.entries(queue_dir).size - 2 rescue 0
  end

  # gets the path to the queue dir for this source
  def queue_dir
    File.join(self.class.queue_parent_dir, id.to_s)
  end
  
  # ensures that the queue dir exists
  def ensure_queue_dir
    FileUtils.mkpath(queue_dir)
  end
  
  # disables the source
  def disable!
    self.enabled = false
    self.save(:validate => false)
  end
  
  def default_update_freq
    case type
    when "TwitterSource" then 10
    when "FacebookGroupSource" then 30
    when "RssSource" then 300
    end
  end
  
  private
    # checks for associated reports before deleteing
    def check_assoc
      raise "There are reports associated with the source '#{name}'." unless reports.empty?
    end
    
    # trims whitespace from keywords in comma delimited string
    def trim_keywords
      self.keywords = keywords ? keywords.split(",").map{|k| k.strip}.join(",") : nil
    end
    
    # makes sure there is only one twitter source (to prevent rate limiting)
    def only_one_twitter
      if type == "TwitterSource" && enabled? && 
        !self.class.find(:first, :conditions => ["type = ? and enabled = ? and id != ?", "TwitterSource", 1, id || 0]).nil?
        errors.add(:base, "There can only be one enabled Twitter source. This is to prevent account suspension due to too many connections.")
      end
    end
    
    # marks the source for restart in other processes if particular fields have changed
    def mark_for_reset
      unless (changed & %w(name url pull_interval keywords enabled update_freq languages username password)).empty?
        self.reset_needed = true
        self.class.create_flag_file
      end
    end

    # creates a source_event and rails log message
    def add_event(level, msg)
      #source_events.create(:level => level, :msg => msg)
      Rails.logger.send(level, "[Source] #{name}: #{msg}")
      crawler.log_event(level, "[Source #{name}]: #{msg}") if crawler
    end
    
    def set_update_freq
      self.update_freq = default_update_freq
    end
    
    def raise_flag
      self.class.create_flag_file
    end
end